package com.liam.library.v8;

import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;

/**
 * JavaScript execution environment,must one instance;
 * @author liaomin
 *
 */
public class JSContext extends JSValue{
	
	static{
		System.loadLibrary("V8Library");
	}
	
	public interface ExceptionHandler {
		public void onException(JSContext context, JSValue exception);
	}
	
	public static abstract class HandleScopeBlock {
		
		protected JSContext context;
		
		/**
		 * the callback when native enter HandleScope
		 */
		public final void enterHandleScope(){
			try {
				this.onEnterHandleScope(context);
			} catch (Exception e) {
				e.printStackTrace();
			}
			//clean temp strong refrence before leave handleScope
			JSValue.clean();
		}
		
		public abstract void onEnterHandleScope(JSContext context);
		
	}
	
	
	private static JSValue jsCallee;
	
	private static JSValue jsThis;
	
	/**
	 * the arraylist which storage the arguments;
	 */
	private static List<JSValue> arguments;


	private static ConcurrentHashMap<Thread, JSContext> contextCache = new ConcurrentHashMap<Thread, JSContext>();
	
	/**
	 * storage runtime exception;
	 */
	private JSValue exception;
	
	private ExceptionHandler exceptionHandler;
	
	private JSValue globalObject;
	
	protected int javaReference;
	
	protected boolean isEnterScope;


	private JSContext() {
		if(contextCache.get(Thread.currentThread()) != null){
			throw new V8Exception("a thread can only have one instance ！");
		}else{
			contextCache.put(Thread.currentThread(), this);
		}
		this.nativeIdentity = nativeInit();
		arguments = new LinkedList<JSValue>();
		isEnterScope = false;
		javaReference = JavaReference.addRefrence(this);
		handleScope(new HandleScopeBlock() {
			@Override
			public void onEnterHandleScope(JSContext context) {
				evaluateScript("Object.prototype.isJavaObject = function(){ return this.__java_object_id__ && this.__java_context_id__  ;}");
			}
		});
	}
	
	protected boolean check(){
		if (!isEnterScope) {
			throw new V8Exception("don't called handleScope methd");
		}
		if(contextCache.get(Thread.currentThread()) != this){
			throw new V8Exception("thread changed!");
		}
		return false;
	}
	
	/**
	 * Evaluate a string of JavaScript code.
	 * @param script A string containing the JavaScript code to evaluate.
	 * @return The last value generated by the script.
	 */
	public JSValue evaluateScript(String script){
		check();
		return JSValue.poll(this,nativeEvaluateScript(nativeIdentity, script));
	}

	/**
	 * Get the JSContext that is currently executing.
	 * @return The currently executing JSContext or nil if there isn't one.
	 */
	public static JSContext currentContext(){
		JSContext context = contextCache.get(Thread.currentThread());
		if(contextCache.size() > 1){
			throw new V8Exception("current version only support singe thread!");
		}
		if(context == null){
			context = new JSContext();
		}
		return context;
	}
	
	/**
	 * Get the JavaScript function that is currently executing.
	 * @return The currently executing JavaScript function or null if there isn't one.
	 */
	public static JSValue currentCallee(){
		return jsCallee;
	}
	
	/**
	 * Get the <code>this</code> value of the currently executing method.
	 * @return The current <code>this</code> value or null if there isn't one.
	 */
	public static JSValue currentThis(){
		return jsThis;
	}
	
	/**
	 * Get the arguments to the current callback.
	 * @return  An ArrayList of the arguments.
	 */
	public static List<JSValue> currentArguments(){
		return arguments;
	}
	

	/**
	 * Get the runtime Exception.
	 * @return the runtime Exception.
	 */
	public JSValue getException() {
		return exception;
	}
	
	/**
	 * Get the global object of the context.
	 * @return The global object,or null if not enter HandleScope.
	 */
	public JSValue getGlobalObject() {
		check();
		return globalObject;
	}
	
	/**
	 * this method will called on native method init success
	 * @param nativeGlobalObjectPtr
	 */
	public void setGlobalObject(long nativeGlobalObjectPtr) {
		globalObject =  new JSValue().setContext(this).setNativeAdress(nativeGlobalObjectPtr);
		globalObject.addRefrence();
	}

	public void setException(long e) {
		JSValue exception = new JSValue().setContext(this).setNativeAdress(e);
		exception.addRefrence();
		if(this.exception != null){
			this.exception.removeRefrence();
		}
		this.exception = exception;
		if(this.exceptionHandler != null){
			this.exceptionHandler.onException(this, exception);
		}
	}
	
	/**
	 * set an exceptionHandler which will be handle when exception throw.
	 * @param exceptionHandler
	 */
	public void setExceptionHandler(ExceptionHandler exceptionHandler) {
		this.exceptionHandler = exceptionHandler;
	}
	
	
	public void handleScope(HandleScopeBlock block){
		isEnterScope = true;
		if(block != null){
			block.context = this;
			nativeHandleScope(nativeIdentity,block);
		}
		isEnterScope = false;
	}
	
	public static void setArguments(long[] args,long jsCalleeL,long jsThisL){
		removeCurrentArguments();
		JSContext instance = currentContext();
		for (int i = 0; i < args.length; i++) {
			arguments.add(JSValue.poll(instance, args[i]));
		}
		jsCallee = JSValue.poll(instance, jsCalleeL);
		jsThis = JSValue.poll(instance, jsThisL);
	}


	protected static void removeCurrentArguments(){
		if(!arguments.isEmpty()){
			for (int i = 0; i < arguments.size(); i++) {
				arguments.get(i).removeRefrence();
			}
			arguments.clear();
		}
		if(jsCallee != null){
			jsCallee.removeRefrence();
			jsCallee = null;
		}
		if(jsThis != null){
			jsThis.removeRefrence();
			jsThis = null;
		}
	}
	
	@Override
	protected void finalize() throws Throwable {
		if(nativeIdentity != 0){
			nativeRelease(nativeIdentity);
			nativeIdentity = 0;
		}
		super.finalize();
	}

	
	private native long nativeInit();
	
	private native void nativeRelease(long address);
	
	private native void nativeHandleScope(long nativeAdress,HandleScopeBlock block);
	
	private native long nativeEvaluateScript(long nativeAdress,String script);
	
}
